// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RGBCameraTest.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Test.Robotics.Vision
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using Microsoft.Robotics.Vision;
    using Microsoft.Robotics.Vision.Cameras;
    using Microsoft.Robotics.Vision.Cameras.Webcam;
    using Microsoft.Robotics.Vision.Runtime.Cameras;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// RGB Camera device related tests.
    /// </summary>
    [TestClass]
    [DeploymentItem("Microsoft.Robotics.Vision.Interop.dll")]
    [DeploymentItem("Microsoft.Robotics.Vision.Native.dll")]  
    public class RGBCameraTest
    {
        /// <summary>
        /// Friendly name of the camera device on the build machines.
        /// </summary>
        private const string CameraFriendlyName = "LifeCam";

        /// <summary>
        /// Enumerates the camera devices and makes sure that the device is present.
        /// You can run this test and watch the output to see the capabilities of your camera device.
        /// This allows you to set the proper resolution on the manifest for your device, if it is a different webcam.
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void RGBCameraDeviceEnumeration()
        {
            RGBCamera device = DetectCamera();
            if (device == null) 
            {
                Assert.Inconclusive(string.Format("Could not find the camera with friendly name {0}", CameraFriendlyName));
                return;
            }

            List<CaptureFormat> formats = new List<CaptureFormat>();

            foreach (CaptureFormat captureFormat in device.Formats)
            {
                formats.Add(captureFormat);
                Debug.WriteLine(
                    "Frame Rate Numerator {0} Frame Rate Denominator {1}  Width {2} Height {3} Subtype {4}",
                    captureFormat.nFrameRateNumerator,
                    captureFormat.nFrameRateDenominator,
                    captureFormat.nWidth,
                    captureFormat.nHeight,
                    captureFormat.subType);
            }

            CaptureFormat format = formats[0];

            List<VideoPropertyValue> properties = new List<VideoPropertyValue>();

            foreach (VideoPropertyValue property in device.VideoProperties)
            {
                properties.Add(property);
                Debug.WriteLine("Property {0} Value {1}", property.m_Property, property.nValue);                
            }

            List<ManagedCameraControlPropertyValue> cameraControlProperties = new List<ManagedCameraControlPropertyValue>();

            foreach (ManagedCameraControlPropertyValue property in device.ManagedCameraControlProperties)
            {
                cameraControlProperties.Add(property);
                Debug.WriteLine("Property {0} Value {1}", property.m_Property, property.nValue);
            }
        }

        /// <summary>
        /// Test creation of a camera connect by name and attributes. Requires a "life cam" web camera connected
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void RGBCameraCreation()
        {
            RGBCamera device = DetectCamera();
            if (device == null)
            {
                Assert.Inconclusive(string.Format("Could not find the camera with friendly name {0}", CameraFriendlyName));
                return;
            }

            string regularExpression = string.Format("{0}{1}{2}", @"[\w\s]*", CameraFriendlyName, @"[.]*");
            RGBCameraConnect rgbCamera = new RGBCameraConnect(640, 480, 15, 1, "YUY2", regularExpression, null);

            // InitializeCamera throws if it does not find a matching camera
            rgbCamera.InitializeCamera();           
        }

        /// <summary>
        /// Ensure that the standard capture formats are enumerated.
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void RGBCameraCaptureFormat()
        {
            RGBCamera device = DetectCamera();
            if (device == null)
            {
                Assert.Inconclusive(string.Format("Could not find the camera with friendly name {0}", CameraFriendlyName));
                return;
            }
            
            CaptureFormat[] captureFormats = new CaptureFormat[3];
            captureFormats[0] = new CaptureFormat() { nFrameRateNumerator = 15, nFrameRateDenominator = 1, nWidth = 640, nHeight = 480, subType = VideoFormat.FromName("YUY2") };
            captureFormats[1] = new CaptureFormat() { nFrameRateNumerator = 15, nFrameRateDenominator = 1, nWidth = 320, nHeight = 240, subType = VideoFormat.FromName("YUY2") };
            captureFormats[2] = new CaptureFormat() { nFrameRateNumerator = 30, nFrameRateDenominator = 1, nWidth = 640, nHeight = 480, subType = VideoFormat.FromName("YUY2") };
            for (int i = 0; i < captureFormats.Length; ++i)
            {
                TestCaptureFormat(device, captureFormats[i]);
            }

            if (device != null)
            {
                device.Shutdown();
            }
        }

        /// <summary>
        /// Ensure that setting denominator to zero throws an argument out of range exception.
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        [ExpectedException(typeof(System.ArgumentOutOfRangeException))]
        public void RGBCameraConnectInvalidFrameRateDenominator()
        {   
            RGBCameraConnect rgbCameraConnect = new RGBCameraConnect(640, 480, 15, 0, "YUY2", string.Empty, null);
        }

        /// <summary>
        /// Test that the video properties are properly enumerated.
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        [Ignore]
        public void RGBCameraVideoProperty()
        {
            RGBCamera device = DetectCamera();
            if (device == null)
            {
                Assert.Inconclusive(string.Format("Could not find the camera with friendly name {0}", CameraFriendlyName));
                return;
            }

            bool found = false;
            string desiredProperty = "Brightness";                      

            foreach (VideoPropertyValue property in device.VideoProperties)
            {             
                Debug.WriteLine("Property {0} Value {1}", property.m_Property.ToString(), property.nValue);
                if (desiredProperty.Equals(property.m_Property.ToString()))
                {
                    Debug.WriteLine(
                        string.Format(
                        "{0}, {1}, {2}, {3}, {4} , {5}, {6}",
                        property.m_Flags,
                        property.m_Property,
                        property.nDefault,
                        property.nMaximum,
                        property.nMaximum,
                        property.nSteppingDelta,
                        property.nValue));
                    found = true;
                    break;
                }
            }

            Assert.IsTrue(
                found, 
                string.Format(
                "Desired Property {0} not found",
                desiredProperty));

            device.SetProperty(VideoProperty.BacklightCompensation, 0, VideoPropertyFlags.Auto);

            device.SetProperty(VideoProperty.Brightness, 83, VideoPropertyFlags.Manual);
            int val = 0;
            int flag = 0;
            bool didGet = device.GetProperty(VideoProperty.Brightness, ref val, ref flag);
            Assert.IsTrue(didGet, "Unable to retrieve RGB camera brightness");
            Assert.AreEqual(val, 83, string.Format("Retrieved brightness {0}, expected brightness 83", val));
            Assert.AreEqual(flag, (int)VideoPropertyFlags.Manual, string.Format("expected flag value of {0}, got {1}", (int)VideoPropertyFlags.Manual, flag));

            if (device != null)
            {
                device.Shutdown();
            }
        }

        /// <summary>
        /// Test that the camera control properties are properly enumerated.
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        [Ignore]
        public void RGBCameraControlProperty()
        {
            RGBCamera device = DetectCamera();
            if (device == null)
            {
                Assert.Inconclusive(string.Format("Could not find the camera with friendly name {0}", CameraFriendlyName));
                return;
            }

            bool found = false;
            string desiredProperty = "Focus";

            foreach (ManagedCameraControlPropertyValue property in device.ManagedCameraControlProperties)
            {
                Debug.WriteLine("Property {0} Value {1}", property.m_Property.ToString(), property.nValue);
                if (desiredProperty.Equals(property.m_Property.ToString()))
                {
                    found = true;
                    break;
                }
            }

            Assert.IsTrue(
                found,
                string.Format(
                "Desired Property {0} not found",
                desiredProperty));

            device.SetProperty(ManagedCameraControlProperty.Focus, 10, ManagedCameraControlPropertyFlags.Manual);
            int val = 0;
            int flag = 0;
            bool didGet = device.GetProperty(ManagedCameraControlProperty.Focus, ref val, ref flag);
            Assert.IsTrue(didGet, "Unable to retrieve RGB camera focus");
            Assert.AreEqual(val, 10, string.Format("Retrieved focus {0}, expected focus 10", val));
            Assert.AreEqual(flag, (int)VideoPropertyFlags.Manual, string.Format("expected flag value of {0}, got {1}", (int)ManagedCameraControlPropertyFlags.Manual, flag));

            if (device != null)
            {
                device.Shutdown();
            }
        }

        /// <summary>
        /// Test to ensure video formats are properly converted from name to GUID and vice versa.
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void RGBCameraVideoFormats()
        {
            Assert.IsTrue(VideoFormat.FromName("YUY2") == VideoFormat.FromGuid(VideoFormat.FromName("YUY2").Guid));
            Assert.IsTrue(VideoFormat.FromName("MJPG") == VideoFormat.FromGuid(VideoFormat.FromName("MJPG").Guid));
            Assert.IsTrue(VideoFormat.FromName("MJPG") != VideoFormat.FromGuid(VideoFormat.FromName("YUY2").Guid));
            Assert.IsTrue(VideoFormat.FromName(string.Empty) == VideoFormat.FromGuid(VideoFormat.FromName(string.Empty).Guid));
        }

        /// <summary>
        /// Test to ensure auto exposure can be set or unset.
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]        
        [Ignore]
        public void RGBCameraAutoExposure()
        {
            RGBCamera device = DetectCamera();
            if (device == null)
            {
                Assert.Inconclusive(string.Format("Could not find the camera with friendly name {0}", CameraFriendlyName));
                return;
            }

            CaptureFormat captureFormat = new CaptureFormat() { nFrameRateNumerator = 15, nFrameRateDenominator = 1, nWidth = 640, nHeight = 480, subType = VideoFormat.FromName("YUY2") };
            TestCaptureFormat(device, captureFormat);

            Assert.IsTrue(device.SetProperty(ManagedCameraControlProperty.Exposure, 0, ManagedCameraControlPropertyFlags.Auto), "Could not set auto exposure to true");
            Assert.IsTrue(device.SetProperty(ManagedCameraControlProperty.Exposure, 2, ManagedCameraControlPropertyFlags.Manual), "Could not set auto exposure to false");

            if (device != null)
            {
                device.Shutdown();
            }
        }

        /// <summary>
        /// Tests YUY2 to Luminance conversions
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public unsafe void RGBCameraDeviceColorConversionYUY2ToLum()
        {
            byte[] yuy2Image = { 1, 2, 3, 4 };
            byte[] lumImage = new byte[2];
            byte[] lumImageExpected = { 1, 3 };
            fixed (byte* yuy2ImagePtr = yuy2Image)
            {
                fixed (byte* lumImagePtr = lumImage)
                {
                    ImageOperations2DInterop.YUY2ToLum(yuy2ImagePtr, (uint)yuy2Image.Length, lumImagePtr, (uint)lumImage.Length);
                }
            }

            Assert.IsTrue(
                lumImageExpected[0] == lumImage[0] && lumImageExpected[1] == lumImage[1],
                "Color conversion failed to return proper luminance image");            
        }

        /// <summary>
        /// Tests YUY2 to Luminance conversion exception
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        [ExpectedException(typeof(System.ArgumentException))]
        public unsafe void RGBCameraDeviceColorConversionYUY2ToLumException()
        {
            byte[] yuy2Image = { 1, 2, 3, 4 };
            byte[] lumImage = new byte[2];
            fixed (byte* yuy2ImagePtr = yuy2Image)
            {
                fixed (byte* lumImagePtr = lumImage)
                {
                    ImageOperations2DInterop.YUY2ToLum(yuy2ImagePtr, (uint)yuy2Image.Length, lumImagePtr, (uint)lumImage.Length - 1);
                }
            }
        }

        /// <summary>
        /// Detect whether the camera required for the test is present and return it if found.
        /// </summary>
        /// <returns>First RGB Camera device whose friendly name contains the CameraFriendlyName string</returns>
        private static RGBCamera DetectCamera()
        {
            RGBCamera.Initialize();

            foreach (var device in RGBCamera.AllDevices)
            {
                if (!device.Attach())
                {
                    continue;
                }

                // There are a few different names for the Microsoft LifeCam, but they all contain LifeCam
                if (device.FriendlyName != null && device.FriendlyName.Contains(CameraFriendlyName))
                {
                    Debug.WriteLine("Found Camera of Type {0}", device.FriendlyName);
                    return device;
                }
            }

            return null;
        }

        /// <summary>
        /// Tests that the given camera format can be set on the camera
        /// </summary>
        /// <param name="device">The RGB camera device</param>
        /// <param name="desiredCaptureFormat">Desired capture format </param>
        /// <returns>True if the capture format is found, false otherwise</returns>
        private static bool TestCaptureFormat(RGBCamera device, CaptureFormat desiredCaptureFormat)
        {
            CaptureFormat desiredFormat = null;
            bool found = false;
            foreach (CaptureFormat captureFormat in device.Formats)
            {
                Debug.WriteLine(
                    "Frame Rate Numerator {0} Frame Rate Denominator {1}  Width {2} Height {3} Subtype {4}",
                    captureFormat.nFrameRateNumerator,
                    captureFormat.nFrameRateDenominator,
                    captureFormat.nWidth,
                    captureFormat.nHeight,
                    captureFormat.subType);
                if (desiredCaptureFormat.nFrameRateNumerator == captureFormat.nFrameRateNumerator
                    && desiredCaptureFormat.nFrameRateDenominator == captureFormat.nFrameRateDenominator
                    && desiredCaptureFormat.nWidth == captureFormat.nWidth
                    && desiredCaptureFormat.nHeight == captureFormat.nHeight
                    && desiredCaptureFormat.subType == captureFormat.subType)
                {
                    desiredFormat = captureFormat;
                    found = true;
                    break;
                }
            }

            Assert.IsTrue(
                found,
                string.Format(
                "Desired frame rate numerator {0} frame rate denominator {1} width {2}, height {3}, subtype {4} combination not found",
                desiredCaptureFormat.nFrameRateNumerator,
                desiredCaptureFormat.nFrameRateDenominator,
                desiredCaptureFormat.nWidth,
                desiredCaptureFormat.nHeight,
                desiredCaptureFormat.subType));

            device.CurrentFormat = desiredFormat;
            Assert.AreEqual(desiredCaptureFormat.nFrameRateNumerator, device.CurrentFormat.nFrameRateNumerator);
            Assert.AreEqual(desiredCaptureFormat.nFrameRateDenominator, device.CurrentFormat.nFrameRateDenominator);
            Assert.AreEqual(desiredCaptureFormat.nWidth, device.CurrentFormat.nWidth);
            Assert.AreEqual(desiredCaptureFormat.nHeight, device.CurrentFormat.nHeight);
            Assert.AreEqual(desiredCaptureFormat.subType, device.CurrentFormat.subType);
            return found;
        }
    }
}
